【Linux C++】线程安全

您所在的位置:网站首页 c++ stl线程安全 【Linux C++】线程安全

【Linux C++】线程安全

2024-07-10 12:05| 来源: 网络整理| 查看: 265

目录

一、为什么我们要使用多线程?

二、线程安全是什么?

三、线程安全的三个体现

      原子性

      可见性

      有序性

四、如何保证线程安全

      1、加锁

      2、原子操作-总线锁(原子操作函数、CAS、C++11atomic类)

       原子操作函数

       CAS指令(compare and swap)

       C11原子类型

      3、线程同步(本文先不讲解)

五、总结

一、为什么我们要使用多线程?

        1)为了解决负载均衡问题,充分利用CPU资源

        2)程序的运行效率可能会提高

二、线程安全是什么?

        线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

三、线程安全的三个体现       原子性

        原子性的操作是不可被中断的一个或一系列操作。

        假设定义一个全局变量,我们需要实现var+1这个操作,这个操作分为三个部分,取到var,var+1,存放var。这三个动作分开来讲的话都是原子操作,如果合在一起而不受外界任何干扰,并且只能有一个线程操作这个变量的话,那么他也是原子性的。

        我们知道多线程和多进程不一样,多进程同时操作同名的全局变量时,他们操作的并不是同一个,也就是说,进程1对全局变量的更改不会影响到进程2。但是多线程不一样,他们操作的是同一个全局变量,线程1极有可能会影响线程2,这就导致了线程安全的问题

        为什么呢?

        我们说过var+1分为三个部分,取值、+1、存放,如果线程1在存放到内存之前,线程2去取这个全局变量,那么线程2取到的就是未+1的var,这个时候线程2对var的操作将没有任何意义。

        使用demo程序测试一下,源码及注释如下:

// 本程序用于"@是星星鸭"博客的测试程序 // 本程序创建了两个线程,让两个线程分别对var进行1000000次+1,那么理想状态下var最终值应该是2000000 // 但是结果中却有很多次并不是2000000,这就说明了该线程的操作不是原子性的,说明在线程1操作var的时候 // 线程2也操作了var。最终导致有很多次重复的结果,所以结果不是2000000 // 但是也有var成功达到2000000的结果,如果你循环10000000次,那么成功的几率就会越来越少。 #include #include #include #include #include void *thmain1(void *arg); // 线程1主函数 void *thmain2(void *arg); // 线程2主函数 int var=0; // 被操作的全局变量 int main(int argc, char* argv[]) { pthread_t thid1,thid2; // 线程1、2的标识id // 创建线程 if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; } if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; } // 等待线程退出 pthread_join(thid1,NULL); pthread_join(thid2,NULL); // 打印出var经过两百次操作的最终值 printf("var=%d\n",var); return 0; } // 线程1、2各自对全局变量进行1000000次加1 void *thmain1(void *arg) { for(int ii=0;ii内存>I/O。

        CPU和内存之间隔着缓存和CPU寄存器。缓存还分为一级、二级、三级缓存。CPU的读写性能上要大于内存,为了提高效率会将数据先取到缓存中,CPU处理完数据后会先放到缓存中,然后同步到内存中。

        这样就会导致一个问题,当我们线程1操作完var之后,他会先放入缓存,在缓存未同步到内存之前,线程2来取var的值,那么取得的就是未修改的值。

        什么是可见?

        如果能保证变量被修改之后会立马存入内存,也就是说如果变量被修改到存入内存中这是一瞬间完成的,那么就成为共享变量的可见性。因为你一修改,内存中的值立马就变了,这样对于其他线程就是可见的变化,但是如果你修改了,等一会内存才变化,那么这中间的过程其他线程是不可见的。

        可见性的解决方案

        使用volatile修饰共享变量,volatile修饰的共享变量在修改后会立即被更新到内存中,其他线程使用共享变量会去内存中读取

        当然还有一种方法是加锁,这里先不说,因为加锁可以解决整个原子性的问题,没必要放在这里解决可见性的问题。

        我们先来思考一下:

        var操作分为取值、+1、赋值,volatile关键字修饰的变量是可见的,也就是使用volatile修饰var的话,在+1之后到赋值这之间的动作就成了原子性操作了。也可以看成是一瞬间完成的,那么如果使用了volatile修饰变量之后,最终值还是达不到两百万,是不是就能说明从取值到+1这之间的操作不是原子性了?

        使用volatile修饰的代码如下:

// 本程序用于"@是星星鸭"博客的测试程序 // 本程序创建了两个线程,让两个线程分别对var进行1000000次+1,那么理想状态下var最终值应该是2000000 // 但是结果中却有很多次并不是2000000,这就说明了该线程的操作不是原子性的,说明在线程1操作var的时候 // 线程2也操作了var。最终导致有很多次重复的结果,所以结果不是2000000 // 但是也有var成功达到2000000的结果,如果你循环10000000次,那么成功的几率就会越来越少。 #include #include #include #include #include void *thmain1(void *arg); // 线程1主函数 void *thmain2(void *arg); // 线程2主函数 volatile int var=0; // 被操作的全局变量 int main(int argc, char* argv[]) { pthread_t thid1,thid2; // 线程1、2的标识id // 创建线程 if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; } if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; } // 等待线程退出 pthread_join(thid1,NULL); pthread_join(thid2,NULL); // 打印出var经过两百次操作的最终值 printf("var=%d\n",var); return 0; } // 线程1、2各自对全局变量进行1000000次加1 void *thmain1(void *arg) { for(int ii=0;ii


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3